package cn.shop.admin.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.shop.admin.entity.PayCallBack;
import cn.shop.admin.entity.excel.WithdrawExcel;
import cn.shop.admin.entity.excel.WithdrawExcelForAliPay;
import cn.shop.admin.entity.excel.WithdrawExcelYun;
import cn.shop.admin.service.*;
import cn.shop.transcation.backPaymentChannelModule.entity.BackPaymentChannel;
import cn.shop.transcation.backPaymentChannelModule.mapper.BackPaymentChannelMapper;
import cn.shop.transcation.backSetting.entity.BackSetting;
import cn.shop.transcation.detail.entity.FundDetail;
import cn.shop.transcation.frontUserModule.FrontUser;
import cn.shop.transcation.merchantUser.mapper.entity.MerchantUser;
import cn.shop.transcation.withdraw.entity.Withdraw;
import cn.shop.transcation.withdraw.mapper.WithdrawMapper;
import cn.shop.utils.PayUtil;
import cn.shop.utils.constant.SystemSettingConstant;
import cn.shop.utils.enums.ProcessState;
import cn.shop.utils.enums.WithdrawChannel;
import cn.shop.utils.enums.WithdrawSolution;
import cn.shop.utils.exception.ServiceException;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.converters.longconverter.LongStringConverter;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 提现(Withdraw)表服务实现类
 *
 * @author jzw
 * @since 2021-11-10 10:06:01
 */
@Slf4j
@Service("withdrawService")
public class WithdrawServiceImpl extends ServiceImpl<WithdrawMapper, Withdraw> implements WithdrawService {

	@Resource
	private FrontUserService frontUserService;

	@Resource
	private MerchantUserService merchantUserService;

	@Resource
	private InboxService inboxService;

	@Resource
	private FundDetailService fundDetailService;

	@Resource
	private BackSettingService backSettingService;

	@Resource
	private RedissonClient redissonClient;
	@Resource
	private BackPaymentChannelMapper channelMapper;
	@Resource
	private OrderRewardService rewardService;
	@Resource
	private WithdrawMapper withdrawMapper;

	/**
	 * 批量删除数据
	 *
	 * @param idList id 集合
	 * @return 操作结果
	 * @author jzw
	 * @since 2021/11/15 14:05
	 */
	@Override
	public boolean batchDelete(List<Long> idList) {
		return SqlHelper.retBool(baseMapper.batchDeleteWithFill(new Withdraw(), Wrappers.<Withdraw>lambdaQuery()
				.in(Withdraw::getId, idList)));
	}

	/**
	 * 导出提现数据
	 *
	 * @param pwd
	 * @param ids
	 * @param response
	 * @param type
	 * @author jzw
	 * @since 2022/1/11 16:57
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public void exportWithdraw(String pwd, List<Long> ids, HttpServletResponse response, String type, String channel) {
		try (ServletOutputStream outputStream = response.getOutputStream()) {
			response.setContentType("application/octet-stream");

			BackSetting backSetting = backSettingService.getOneByKey(SystemSettingConstant.ADMIN_TRANSACTION_PASSWORD);

			String transactionPassword = backSetting.getValue();
			if (StrUtil.isBlank(transactionPassword)) {
				response.setHeader("message", URLEncoder.encode("请先设置交易密码", "utf-8"));
				return;
			}
			if (!DigestUtil.bcryptCheck(pwd, transactionPassword)) {
				response.setHeader("message", URLEncoder.encode("交易密码不正确", "utf-8"));
				return;
			}
			if (CollectionUtil.isEmpty(ids)) {
				response.setHeader("message", URLEncoder.encode("请选择订单", "utf-8"));
				return;
			}

			List<Withdraw> withdraws = baseMapper.selectBatchIds(ids);
			List<Long> untreatedIds = withdraws.stream()
					.filter(withdraw -> withdraw.getState().equals(ProcessState.UNTREATED))
					.map(Withdraw::getId)
					.collect(Collectors.toList());
			if (!untreatedIds.isEmpty()) {
				LambdaUpdateWrapper<Withdraw> updateWrapper = Wrappers.lambdaUpdate();
				updateWrapper.in(Withdraw::getId, untreatedIds).eq(Withdraw::getState, ProcessState.UNTREATED);
				//  将导出的数据改为 提现中
				updateWrapper.set(Withdraw::getState, ProcessState.PROCESSING);
				updateWrapper.set(Withdraw::getSolution, WithdrawSolution.SELF_HELP);
				baseMapper.update(new Withdraw(), updateWrapper);
			}
			if ("银行卡".equals(type)) {
				// 设置下载的文件名
				response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("提现导出" + DateUtil.now() + ".xlsx", "utf-8"));
				if (StrUtil.equals("yun", channel)) {
					List<WithdrawExcelYun> collect = withdraws.stream()
							.map(e -> {
								WithdrawExcelYun yun = new WithdrawExcelYun();
								yun.setId(e.getId());
								yun.setAccount(e.getAccount());
								switch (e.getBankName()) {
									case "工商银行":
										yun.setBankName("中国工商银行");
										break;
									case "农业银行":
										yun.setBankName("中国农业银行");
										break;
									case "建设银行":
										yun.setBankName("中国建设银行");
										break;
									case "广发银行":
										yun.setBankName("广东发展银行");
										break;
									default:
										yun.setBankName(e.getBankName());
								}
								yun.setAmount(e.getActualAmount());
								yun.setUserName(e.getBankAccountName());
								return yun;
							}).collect(Collectors.toList());
					EasyExcel.write(outputStream, WithdrawExcelYun.class)
							.registerConverter(new LongStringConverter())
							.sheet()
							.doWrite(collect);
				} else {
					List<WithdrawExcel> collect = withdraws.stream()
							.map(e -> new WithdrawExcel(e.getBankName(), e.getBankAccountName(), e.getAccount(), e.getActualAmount(), e.getIdCard(), e.getId())).collect(Collectors.toList());
					EasyExcel.write(outputStream, WithdrawExcel.class)
							.registerConverter(new LongStringConverter())
							.sheet()
							.doWrite(collect);
				}
			}
			else if ("支付宝".equals(type)) {
				List<WithdrawExcelForAliPay> collect = withdraws.stream().map(e -> new WithdrawExcelForAliPay(e.getAccount(), e.getBankAccountName(), e.getActualAmount(), e.getId(), e.getAccount())).collect(Collectors.toList());
				// 设置下载的文件名
				response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode("提现导出" + DateUtil.now() + ".xlsx", "utf-8"));
				EasyExcel.write(outputStream, WithdrawExcelForAliPay.class).registerConverter(new LongStringConverter()).sheet().relativeHeadRowIndex(1).doWrite(collect);

			}

			// 不需要 close , 这里使用的是jdk8 的 try catch resources  会自动 close
			outputStream.flush();
		} catch (IOException e) {
			throw new ServiceException("导出失败");
		}


	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean sendBackWithdraw(List<Long> ids) {
		List<Withdraw> withdraws = listByIds(ids);
		if (CollectionUtil.isEmpty(withdraws)) {
			return false;
		}
		List<Withdraw> updates = new ArrayList<>();
		List<FundDetail> details = new ArrayList<>();
		for (Withdraw withdraw : withdraws) {
			if (withdraw.getState() == ProcessState.COMPLETED) {
				continue;
			}
			if (withdraw.getType() == 1) {
				MerchantUser user = merchantUserService.getById(withdraw.getUserId());
				if (user == null) {
					continue;
				}
				LambdaUpdateWrapper<MerchantUser> lambdaUpdate = Wrappers.lambdaUpdate();
				lambdaUpdate.eq(MerchantUser::getId, user.getId()).
						setSql("balance = balance + " + withdraw.getAmount());
				merchantUserService.update(lambdaUpdate);

				FundDetail detail = new FundDetail();
				detail.setType(4);
				detail.setPlayerId(withdraw.getUserId());
				detail.setOrderId(withdraw.getId());
				detail.setPlayerAccount(user.getAccount());
				detail.setPlatformShare(withdraw.getCharges().negate());
				detail.setBalance(user.getBalance().add(withdraw.getAmount()));
				detail.setAmount(withdraw.getAmount());
				detail.setGoods("商户提现退款");
				detail.setDealTime(LocalDateTime.now());
				details.add(detail);
			} else {
				FrontUser user = frontUserService.getById(withdraw.getUserId());
				if (user == null) {
					continue;
				}
				LambdaUpdateWrapper<FrontUser> lambdaUpdate1 = Wrappers.lambdaUpdate();
				lambdaUpdate1.eq(FrontUser::getId, user.getId()).
						setSql("balance = balance + " + withdraw.getAmount());
				frontUserService.update(lambdaUpdate1);
				FundDetail detail = new FundDetail();
				detail.setType(4);
				detail.setPlayerId(withdraw.getUserId());
				detail.setOrderId(withdraw.getId());
				detail.setPlayerAccount(user.getAccount());
				detail.setPlatformShare(withdraw.getCharges().negate());
				detail.setAmount(withdraw.getAmount());
				detail.setBalance(user.getBalance().add(withdraw.getAmount()));
				detail.setGoods("玩家提现退款");
				detail.setDealTime(LocalDateTime.now());
				details.add(detail);
			}
			if (withdraw.getChannel() == WithdrawChannel.APP) {
				rewardService.unreward(withdraw);
			}
			Withdraw update = new Withdraw();
			update.setId(withdraw.getId());
			update.setSolution(WithdrawSolution.SEND_BACK);
			// 因为 未结算的提现也可以退回 但是退回并不改变状态 所以这里强制将状态改成异常
			update.setState(ProcessState.ABNORMAL);
			updates.add(update);
		}
		updateBatchById(updates);
		fundDetailService.saveBatch(details);
		return true;
	}

	@Override
	@Transactional
	public boolean completePayment(List<Long> ids) {
		return lambdaUpdate().in(Withdraw::getId, ids)
				.eq(Withdraw::getState, ProcessState.PROCESSING)
				.set(Withdraw::getState, ProcessState.COMPLETED)
				.set(Withdraw::getPaymentTime, LocalDateTime.now())
				.update();
	}

	@Override
	public String notify(PayCallBack payCallback) {
		RLock lock = redissonClient.getLock("notify:" + payCallback.getRequestId());
		try {
			if (lock.tryLock(0, 30, TimeUnit.SECONDS)) {
				LambdaQueryWrapper<Withdraw> query = Wrappers.lambdaQuery();
				query.eq(Withdraw::getId, payCallback.getRequestId()).eq(Withdraw::getState, ProcessState.PROCESSING);
				Withdraw withdraw = getOne(query);
				//校验订单
				if (withdraw == null) {
					log.error("订单无效: {}", payCallback);
					return "SUCCESS";
				}
				BackPaymentChannel channel = channelMapper.selectById(withdraw.getPaymentChannelId());
				if (channel == null || channel.getType() == null
						|| StrUtil.hasBlank(channel.getSecret())
						|| channel.getMerchantNumber() == null) {
					throw new ServiceException("支付渠道号错误");
				}
				Dict dict = Dict.create();
				dict.set("amount", payCallback.getAmount()).
						set("requestId", payCallback.getRequestId()).
						set("serialNumber", payCallback.getSerialNumber()).
						set("status", payCallback.getStatus()).
						set("merchantId", payCallback.getMerchantId());
				//校验签名
				if (ObjectUtil.notEqual(PayUtil.getSignToken(dict, channel.getSecret()), payCallback.getCode())) {
					log.error("代付订单:{} , 支付订单:{}, 回调签名认证失败", payCallback.getRequestId(), payCallback.getSerialNumber());
					return "SUCCESS";
				}

				// 订单已经处理过 , 直接返回success
				if (withdraw.getState().getState() >= ProcessState.COMPLETED.getState()) {
					log.info("代付订单:{} , 支付订单:{}, 代付成功", payCallback.getRequestId(), payCallback.getSerialNumber());
					return "SUCCESS";
				}
				//校验金额
				if (!NumberUtil.equals(payCallback.getAmount(), NumberUtil.mul(withdraw.getActualAmount(), 100))) {
					// throw new ServiceException("付款金额与订单金额不符");
					log.error("代付订单:{} , 支付订单:{}, 付款金额与订单金额不符", payCallback.getRequestId(), payCallback.getSerialNumber());
					return "SUCCESS";
				}
				if ("SUCCESS".equals(payCallback.getStatus())) {
					// 修改充值状态
					withdraw.setState(ProcessState.COMPLETED);
					withdraw.setPaymentTime(LocalDateTime.now());
				} else {
					withdraw.setState(ProcessState.ABNORMAL);
					withdraw.setExceptionInfo("代付回调失败");
				}
				updateById(withdraw);
			} else {
				log.error("代付订单:{} , 支付订单:{}, 正在处理中", payCallback.getRequestId(), payCallback.getSerialNumber());
				return "SUCCESS";
			}
		} catch (Exception e) {
			log.error("代付订单:{} , 支付订单:{}, 支付回调失败,错误信息 :{} ", payCallback.getRequestId(), payCallback.getSerialNumber(), e.getMessage());
			return "SUCCESS";
		} finally {
			if (lock.isLocked() && lock.isHeldByCurrentThread()) {
				lock.unlock();
			}
		}
		log.info("代付订单:{} , 支付订单:{}, 支付回调处理完成", payCallback.getRequestId(), payCallback.getSerialNumber());
		return "SUCCESS";
	}

	@Override
	public Map<String, Object> getStatistics(QueryWrapper query) {
		return withdrawMapper.getStatistics(query);
	}
}

